#include <include/x86.h>
#include <include/mmu.h>
#include <include/error.h>
#include <include/string.h>
#include <include/assert.h>
#include <include/elf.h>

#include <kernel/proc.h>
#include <mm/mm.h>
#include <kernel/trap.h>
#include <kernel/monitor.h>

struct Proc *procs;
struct Proc *curproc;

void
proc_init(void)
{
	int i;
	for (i = NR_PROC-1; i >= 0; i--) {
		procs[i].pid = 0;
	}
}

//
// Initialize the kernel virtual memory layout for process p.
// Allocate a page directory, set p->proc_pgdir and p->proc_cr3 accordingly,
// and initialize the kernel portion of the new process's address space.
// Do NOT (yet) map anything into the user portion
// of the process's virtual address space.
//
// Returns 0 on success, < 0 on error.  Errors include:
//	-E_NO_MEM if page directory or table could not be allocated.
//
static int
proc_setup_vm(struct Proc *proc)
{
	int i, r;
	struct Page *p = NULL;

	// Allocate a page for the page directory
	if ((r = page_alloc(&p)) < 0)
		return r;

	p->pp_ref++;
	proc->proc_pgdir = page2kva(p);
	proc->proc_cr3 = PADDR(proc->proc_pgdir);
	memset(proc->proc_pgdir, 0, PGSIZE);
	memmove(proc->proc_pgdir + PDX(UTOP), kernel_pgdir + PDX(UTOP), PGSIZE - PDX(UTOP) * 4);

	return 0;
}

//
// Allocates and initializes a new process.
// On success, the new  process is stored in *new_proc.
//
// Returns 0 on success, < 0 on failure.  Errors include:
//	-E_NO_FREE_PROC if all NENVS environments are allocated
//	-E_NO_MEM on memory exhaustion
//
int
proc_alloc(struct Proc **new_proc, pid_t parent_id)
{
	int r,i;
	struct Proc *proc=NULL;

	for (i=0;i<NR_PROC;i++) {
		if (procs[i].pid==0) {
			proc=&procs[i];
			break;
		}
	}
	if (i==NR_PROC)
		return -E_NO_FREE_PROC;

	// Allocate and set up the page directory for this process.
	if ((r = proc_setup_vm(proc)) < 0)
		return r;

	// assign an pid for this process.
	proc->pid = parent_id;

	// Set the basic status variables.
	proc->ppid = parent_id;
	proc->proc_status = PROC_RUNNABLE;
	proc->proc_runs = 0;

	// Clear out all the saved register state,
	// to prevent the register values
	// of a prior environment inhabiting this Env structure
	// from "leaking" into our new environment.
	memset(&proc->proc_tf, 0, sizeof(proc->proc_tf));

	// Set up appropriate initial values for the segment registers.
	// GD_UD is the user data segment selector in the GDT, and 
	// GD_UT is the user text segment selector (see inc/memlayout.h).
	// The low 2 bits of each segment register contains the
	// Requestor Privilege Level (RPL); 3 means user mode.
	proc->proc_tf.tf_gs = GD_KD | 0;
	proc->proc_tf.tf_fs = GD_KD | 0;
	proc->proc_tf.tf_ds = GD_KD | 0;
	proc->proc_tf.tf_es = GD_KD | 0;
	proc->proc_tf.tf_ss = GD_KD | 0;
	proc->proc_tf.tf_esp = USTACKTOP;
	proc->proc_tf.tf_cs = GD_KT | 0;
	// You will set e->env_tf.tf_eip later.

	// Enable interrupts while in user mode.
	proc->proc_tf.tf_eflags |= FL_IF;

	// give it I/O privileges.
	proc->proc_tf.tf_eflags |= FL_IOPL_3;

	*new_proc = proc;

	cprintf("[%08x] new env %08x\n", curproc ? curproc->pid : 0, proc->pid);
	return 0;
}

//
// Allocate len bytes of physical memory for process proc,
// and map it at virtual address va in the process's address space.
// Does not zero or otherwise initialize the mapped pages in any way.
// Pages should be writable by user and kernel.
// Panic if any allocation attempt fails.
//
static void
segment_alloc(struct Proc *proc, void *va, size_t len)
{
	//   It is easier to use segment_alloc if the caller can pass
	//   'va' and 'len' values that are not page-aligned.
	//   You should round va down, and round len up.
	struct Page *p;
	void *va_end;
	int r;

	va_end = va + len;
	va = ROUNDDOWN(va, PGSIZE);
	while (va < va_end) {
		if ((r = page_alloc(&p)) < 0)
			panic("segment_alloc: %e", r);

		if ((r = page_insert(proc->proc_pgdir, p, (uintptr_t)va, PTE_W |PTE_U)) < 0)
			panic("segment_alloc: %e", r);

		va += PGSIZE;
	}
}

static void
load_icode(struct Proc *proc, uint8_t *binary, size_t size)
{
	int r;
	struct Page *p;

	// just for test.. we limit the binary(just a hook) to PGSIZE...
	//segment_alloc(proc, (void *)binary, PGSIZE);

	proc->proc_tf.tf_eip = (uintptr_t)binary;

	// Now map one page for the program's initial stack
	// at virtual address USTACKTOP - PGSIZE.
	if ((r = page_alloc(&p)) < 0)
		panic("load_icode: %e", r);
	if ((r = page_insert(proc->proc_pgdir,
				p,
				(uintptr_t)(USTACKTOP - PGSIZE),
				PTE_W | PTE_U)) < 0)
		panic("load_icode: %e", r);
}

//
// Frees process proc.
// If proc was the current proc, then runs a new process (and does not return
// to the caller).
//
void
proc_destroy(struct Proc *proc) 
{
}


//
// Restores the register values in the Trapframe with the 'iret' instruction.
// This exits the kernel and starts executing some process's code.
// This function does not return.
//
void
proc_pop_tf(struct Trapframe *tf)
{
	__asm __volatile("movl %0,%%esp\n"
			 "\tpopal\n"
			 "\tpopl %%gs\n"
			 "\tpopl %%fs\n"
			 "\tpopl %%es\n"
			 "\tpopl %%ds\n"
			 "\taddl $0x8,%%esp\n" /* skip tf_trapno and tf_errcode */
			 "\tiret"
			 : : "g" (tf) : "memory");
	panic("iret failed");  /* mostly to placate the compiler */
}

//
// Context switch from curenv to process proc.
// Note: if this is the first call to env_run, curenv is NULL.
//  (This function does not return.)
//
void
proc_run(struct Proc *proc)
{
	curproc = proc;
	proc->proc_runs++;
	lcr3(proc->proc_cr3);

	proc_pop_tf(&proc->proc_tf);
}

void
proc_create(uint8_t *p)
{
	struct Proc *proc;
	proc_alloc(&proc,0);
	load_icode(proc,p,PGSIZE);
}
